์น์์ ์ธํฐ๋ํฐ๋ธํ๊ณ ์ง๊ด์ ์ธ ์ฆ๊ฐ ํ์ค ๋ฐ ๊ฐ์ ํ์ค ๊ฒฝํ์ ๋ง๋๋ ๋ฐ ์ค์ํ WebXR ํํธ ํ ์คํธ ๊ฒฐ๊ณผ ๋ฐ ๋ ์ด ์บ์คํ ์ฒ๋ฆฌ์ ๋ํ ์ฌ์ธต ๋ถ์์ ๋๋ค.
WebXR ํํธ ํ ์คํธ ๊ฒฐ๊ณผ: ๋ชฐ์ ํ ๊ฒฝํ์ ์ํ ๋ ์ด ์บ์คํ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
WebXR Device API๋ ๋ธ๋ผ์ฐ์ ๋ด์์ ์ง์ ๋ชฐ์ ํ ์ฆ๊ฐ ํ์ค(AR) ๋ฐ ๊ฐ์ ํ์ค(VR) ๊ฒฝํ์ ๋ง๋ค ์ ์๋ ํฅ๋ฏธ๋ก์ด ๊ฐ๋ฅ์ฑ์ ์ด์ด์ค๋๋ค. ๋ํํ WebXR ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ๊ธฐ๋ณธ ์ธก๋ฉด ์ค ํ๋๋ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ดํดํ๊ณ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ๊ฒ์ ๋๋ค. ์ด ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ๋ ์ด ์บ์คํ ์ ํตํด ์ป์ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํ ํฌ๊ด์ ์ธ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํ์ฌ WebXR ์ฅ๋ฉด ๋ด์์ ์ง๊ด์ ์ด๊ณ ๋งค๋ ฅ์ ์ธ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ๋ง๋ค ์ ์๋๋ก ํฉ๋๋ค.
๋ ์ด ์บ์คํ ์ด๋ ๋ฌด์์ด๋ฉฐ WebXR์์ ์ ์ค์ํ ๊น์?
๋ ์ด ์บ์คํ ์ ํน์ ์ง์ ๊ณผ ๋ฐฉํฅ์์ ์์๋๋ ๋ ์ด๊ฐ 3D ์ฅ๋ฉด์ ๊ฐ์ฒด์ ๊ต์ฐจํ๋์ง ํ์ธํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๊ธฐ์ ์ ๋๋ค. WebXR์์ ๋ ์ด ์บ์คํ ์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์์ ์์ ์ด๋ ๊ฐ์ ๊ฐ์ฒด์ ๊ถค์ ์ ์๋ฎฌ๋ ์ด์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ ์ด๊ฐ ์ค์ ํ๋ฉด(AR์์) ๋๋ ๊ฐ์ ๊ฐ์ฒด(VR์์)๊ณผ ๊ต์ฐจํ๋ฉด ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๊ฐ ์์ฑ๋ฉ๋๋ค.
ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์ด์ ๋ก ์ค์ํฉ๋๋ค.
- ๊ฐ์ ๊ฐ์ฒด ๋ฐฐ์น: AR์์ ํํธ ํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ฉด ํ ์ด๋ธ, ๋ฐ๋ฅ ๋๋ ๋ฒฝ๊ณผ ๊ฐ์ ์ค์ ํ๋ฉด์ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ ํํ๊ฒ ๋ฐฐ์นํ ์ ์์ต๋๋ค.
- ์ฌ์ฉ์ ์ํธ ์์ฉ: ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์๊ฑฐ๋ ๊ฐ๋ฆฌํค๋ ์์น๋ฅผ ์ถ์ ํจ์ผ๋ก์จ ํํธ ํ ์คํธ๋ฅผ ํตํด ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ ํ, ์กฐ์ ๋๋ ํ์ฑํํ๋ ๋ฑ์ ์ํธ ์์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ํ์: VR ํ๊ฒฝ์์ ํํธ ํ ์คํธ๋ ํ์ ์์คํ ์ ๊ตฌํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ ํน์ ์์น๋ฅผ ๊ฐ๋ฆฌ์ผ ์๊ฐ ์ด๋ํ๊ฑฐ๋ ์ฅ๋ฉด ์ฃผ์๋ฅผ ์ด๋ํ ์ ์์ต๋๋ค.
- ์ถฉ๋ ๊ฐ์ง: ํํธ ํ ์คํธ๋ ๊ธฐ๋ณธ ์ถฉ๋ ๊ฐ์ง์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๊ฐ์ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด ๋๋ ์ค์ ์ธ๊ณ์ ์ถฉ๋ํ๋ ์์ ์ ๊ฒฐ์ ํฉ๋๋ค.
WebXR ํํธ ํ ์คํธ API ์ดํด
WebXR ํํธ ํ ์คํธ API๋ ๋ ์ด ์บ์คํ ์ ์ํํ๊ณ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ป๋ ๋ฐ ํ์ํ ๋๊ตฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ฃผ์ ๊ฐ๋ ๋ฐ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
XRRay
XRRay๋ 3D ๊ณต๊ฐ์์ ๋ ์ด๋ฅผ ๋ํ๋
๋๋ค. ์ด๋ ์์ ๊ณผ ๋ฐฉํฅ ๋ฒกํฐ๋ก ์ ์๋ฉ๋๋ค. XRFrame.getPose() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ XRRay๋ฅผ ๋ง๋ค ์ ์์ผ๋ฉฐ, ์ด ๋ฉ์๋๋ ์ถ์ ๋ ์
๋ ฅ ์์ค(์: ์ฌ์ฉ์์ ๋จธ๋ฆฌ, ํธ๋ ์ปจํธ๋กค๋ฌ)์ ํฌ์ฆ๋ฅผ ๋ฐํํฉ๋๋ค. ํฌ์ฆ์์ ๋ ์ด์ ์์ ๊ณผ ๋ฐฉํฅ์ ํ์ํ ์ ์์ต๋๋ค.
XRHitTestSource
XRHitTestSource๋ ํํธ ํ
์คํธ ๊ฒฐ๊ณผ์ ์์ค๋ฅผ ๋ํ๋
๋๋ค. XRSession.requestHitTestSource() ๋๋ XRSession.requestHitTestSourceForTransientInput() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํํธ ํ
์คํธ ์์ค๋ฅผ ๋ง๋ญ๋๋ค. ์ฒซ ๋ฒ์งธ ๋ฉ์๋๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์์ ๋จธ๋ฆฌ ์์น์ ๊ฐ์ ์ง์์ ์ธ ์์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ง์์ ์ธ ํํธ ํ
์คํธ์ ์ฌ์ฉ๋๋ ๋ฐ๋ฉด, ๋ ๋ฒ์งธ ๋ฉ์๋๋ ๋ฒํผ ๋๋ฆ ๋๋ ์ ์ค์ฒ์ ๊ฐ์ ์ผ์์ ์ธ ์
๋ ฅ ์ด๋ฒคํธ์ ์ฌ์ฉ๋ฉ๋๋ค.
XRHitTestResult
XRHitTestResult๋ ๋ ์ด์ ํ๋ฉด ์ฌ์ด์ ๋จ์ผ ๊ต์ฐจ์ ์ ๋ํ๋
๋๋ค. ๋ ์ด ์์ ์์ ํํธ ์ง์ ๊น์ง์ ๊ฑฐ๋ฆฌ์ ์ฅ๋ฉด์ ์ฐธ์กฐ ๊ณต๊ฐ์์ ํํธ ์ง์ ์ ํฌ์ฆ์ ๊ฐ์ ๊ต์ฐจ์ ์ ๋ํ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
XRHitTestResult.getPose()
์ด ๋ฉ์๋๋ ํํธ ์ง์ ์ XRPose๋ฅผ ๋ฐํํฉ๋๋ค. ํฌ์ฆ์๋ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ฐฐ์นํ๊ฑฐ๋ ๋ค๋ฅธ ๋ณํ์ ์ํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ํํธ ์ง์ ์ ์์น์ ๋ฐฉํฅ์ด ํฌํจ๋ฉ๋๋ค.
ํํธ ํ ์คํธ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ: ๋จ๊ณ๋ณ ๊ฐ์ด๋
WebXR ์ ํ๋ฆฌ์ผ์ด์ ์์ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ณ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ์์ ์์๋ three.js ๋๋ Babylon.js์ ๊ฐ์ ๋ ๋๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
1. ํํธ ํ ์คํธ ์์ค ์์ฒญ
๋จผ์ XRSession์์ ํํธ ํ
์คํธ ์์ค๋ฅผ ์์ฒญํด์ผ ํฉ๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก ์ธ์
์ด ์์๋ ํ์ ์ํ๋ฉ๋๋ค. ํํธ ํ
์คํธ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋ ์ขํ๊ณ๋ฅผ ์ง์ ํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด:
let xrHitTestSource = null;
async function createHitTestSource(xrSession) {
try {
xrHitTestSource = await xrSession.requestHitTestSource({
space: xrSession.viewerSpace // Or xrSession.local
});
} catch (error) {
console.error("Failed to create hit test source: ", error);
}
}
// Call this function after the XR session has started
// createHitTestSource(xrSession);
์ค๋ช :
xrSession.requestHitTestSource(): ์ด ํจ์๋ XR ์ธ์ ์์ ํํธ ํ ์คํธ ์์ค๋ฅผ ์์ฒญํฉ๋๋ค.{ space: xrSession.viewerSpace }: ์ด๋ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋ ์ขํ๊ณ๋ฅผ ์ง์ ํฉ๋๋ค.viewerSpace๋ ๋ทฐ์ด์ ์์น๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๋ ๋ฐ๋ฉดlocal์ XR ์์ ์ ๊ธฐ์ค์ผ๋ก ํฉ๋๋ค. ๋ฐ๋ฅ์ ๊ธฐ์ค์ผ๋ก ์ถ์ ํ๋ ค๋ฉดlocalFloor๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.- ์ค๋ฅ ์ฒ๋ฆฌ:
try...catch๋ธ๋ก์ ํํธ ํ ์คํธ ์์ค ์์ฑ ์ค์ ๋ฐ์ํ ์ค๋ฅ๊ฐ ํฌ์ฐฉ๋๊ณ ๊ธฐ๋ก๋๋๋ก ํฉ๋๋ค.
2. ์ ๋๋ฉ์ด์ ๋ฃจํ์์ ํํธ ํ ์คํธ ์ํ
์ ๋๋ฉ์ด์
๋ฃจํ(๊ฐ ํ๋ ์์ ๋ ๋๋งํ๋ ํจ์) ๋ด์์ XRFrame.getHitTestResults() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํํธ ํ
์คํธ๋ฅผ ์ํํด์ผ ํฉ๋๋ค. ์ด ๋ฉ์๋๋ ์ฅ๋ฉด์ ์๋ ๋ชจ๋ ๊ต์ฐจ์ ์ ๋ํ๋ด๋ XRHitTestResult ๊ฐ์ฒด์ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
function onXRFrame(time, frame) {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);
const pose = frame.getViewerPose(xrSession.referenceSpace);
if (pose) {
if (xrHitTestSource) {
const hitTestResults = frame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
processHitTestResults(hitTestResults);
}
}
}
renderer.render(scene, camera);
}
์ค๋ช :
frame.getViewerPose(xrSession.referenceSpace): ๋ทฐ์ด(ํค๋์ )์ ํฌ์ฆ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ๋ทฐ์ด๊ฐ ์ด๋์ ์๊ณ ๋ฌด์์ ๋ณด๊ณ ์๋์ง ์๊ธฐ ์ํด ํ์ํฉ๋๋ค.frame.getHitTestResults(xrHitTestSource): ์ด์ ์ ์์ฑ๋ ํํธ ํ ์คํธ ์์ค๋ฅผ ์ฌ์ฉํ์ฌ ํํธ ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค.hitTestResults.length > 0: ๊ต์ฐจ์ ์ด ์๋์ง ํ์ธํฉ๋๋ค.
3. ํํธ ํ ์คํธ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
processHitTestResults() ํจ์๋ ํํธ ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ณณ์
๋๋ค. ์ฌ๊ธฐ์๋ ์ผ๋ฐ์ ์ผ๋ก ํํธ ์ง์ ์ ํฌ์ฆ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ ๊ฐ์ฒด์ ์์น์ ๋ฐฉํฅ์ ์
๋ฐ์ดํธํ๋ ์์
์ด ํฌํจ๋ฉ๋๋ค.
function processHitTestResults(hitTestResults) {
const hit = hitTestResults[0]; // Get the first hit result
const hitPose = hit.getPose(xrSession.referenceSpace);
if (hitPose) {
// Update the position and orientation of a virtual object
virtualObject.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z);
virtualObject.quaternion.set(hitPose.transform.orientation.x, hitPose.transform.orientation.y, hitPose.transform.orientation.z, hitPose.transform.orientation.w);
// Show visual feedback (e.g., a circle) at the hit point
hitMarker.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z);
hitMarker.quaternion.set(hitPose.transform.orientation.x, hitPose.transform.orientation.y, hitPose.transform.orientation.z, hitPose.transform.orientation.w);
hitMarker.visible = true;
} else {
hitMarker.visible = false;
}
}
์ค๋ช :
hitTestResults[0]: ์ฒซ ๋ฒ์งธ ํํธ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์ํฉ๋๋ค. ์ฌ๋ฌ ๊ต์ฐจ๊ฐ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์ ์ฒด ๋ฐฐ์ด์ ๋ฐ๋ณตํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ผ๋ฆฌ์ ๋ฐ๋ผ ๊ฐ์ฅ ์ ํฉํ ๊ฒฐ๊ณผ๋ฅผ ์ ํํด์ผ ํ ์ ์์ต๋๋ค.hit.getPose(xrSession.referenceSpace): ์ง์ ๋ ์ฐธ์กฐ ๊ณต๊ฐ์์ ํํธ ์ง์ ์ ํฌ์ฆ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.virtualObject.position.set(...)๋ฐvirtualObject.quaternion.set(...): ํํธ ์ง์ ์ ํฌ์ฆ์ ์ผ์นํ๋๋ก ๊ฐ์ ๊ฐ์ฒด(์: three.jsMesh)์ ์์น์ ํ์ (์ฌ์์)์ ์ ๋ฐ์ดํธํฉ๋๋ค.- ์๊ฐ์ ํผ๋๋ฐฑ: ์ด ์์ ์๋ ์ฌ์ฉ์๊ฐ ์ฅ๋ฉด์ ๋ํด ์ํธ ์์ฉํ๋ ์์น๋ฅผ ์ดํดํ๋ ๋ฐ ๋์์ด ๋๋๋ก ์ ๋๋ ๊ฐ๋จํ ๋ง์ปค์ ๊ฐ์ ํํธ ์ง์ ์์ ์๊ฐ์ ํผ๋๋ฐฑ์ ํ์ํ๋ ์ฝ๋๋ ํฌํจ๋์ด ์์ต๋๋ค.
๊ณ ๊ธ ํํธ ํ ์คํธ ๊ธฐ์
์์ ๊ธฐ๋ณธ ์์ ์ธ์๋ ํํธ ํ ์คํธ ๊ตฌํ์ ํฅ์์ํค๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ๋ช ๊ฐ์ง ๊ณ ๊ธ ๊ธฐ์ ์ด ์์ต๋๋ค.
์ผ์์ ์ ๋ ฅ์ผ๋ก ํํธ ํ ์คํธ
๋ฒํผ ๋๋ฆ ๋๋ ์ ์ ์ค์ฒ์ ๊ฐ์ ์ผ์์ ์
๋ ฅ์ผ๋ก ํธ๋ฆฌ๊ฑฐ๋๋ ์ํธ ์์ฉ์ ๊ฒฝ์ฐ XRSession.requestHitTestSourceForTransientInput() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ ๋จ์ผ ์
๋ ฅ ์ด๋ฒคํธ์ ํน์ ์ ์ธ ํํธ ํ
์คํธ ์์ค๋ฅผ ์์ฑํฉ๋๋ค. ์ด๋ ์ง์์ ์ธ ํํธ ํ
์คํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ์๋ํ์ง ์์ ์ํธ ์์ฉ์ ๋ฐฉ์งํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
async function handleSelect(event) {
try {
const frame = event.frame;
const inputSource = event.inputSource;
const hitTestResults = await frame.getHitTestResultsForTransientInput(inputSource, {
profile: 'generic-touchscreen', // Or the appropriate input profile
space: xrSession.viewerSpace
});
if (hitTestResults.length > 0) {
processHitTestResults(hitTestResults);
}
} catch (error) {
console.error("Error during transient hit test: ", error);
}
}
// Attach this function to your input select event listener
// xrSession.addEventListener('select', handleSelect);
ํํธ ํ ์คํธ ๊ฒฐ๊ณผ ํํฐ๋ง
๊ฒฝ์ฐ์ ๋ฐ๋ผ ๋ ์ด ์์ ์ผ๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ ๋๋ ๊ต์ฐจ๋ ํ๋ฉด์ ์ ํ๊ณผ ๊ฐ์ ํน์ ๊ธฐ์ค์ ๋ฐ๋ผ ํํธ ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ํํฐ๋งํ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด์๋ ํํธ ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ํ XRHitTestResult ๋ฐฐ์ด์ ์๋์ผ๋ก ํํฐ๋งํ๋ฉด ๋ฉ๋๋ค.
function processHitTestResults(hitTestResults) {
const filteredResults = hitTestResults.filter(result => {
const hitPose = result.getPose(xrSession.referenceSpace);
if (!hitPose) return false; // Skip if no pose
const distance = Math.sqrt(
Math.pow(hitPose.transform.position.x - camera.position.x, 2) +
Math.pow(hitPose.transform.position.y - camera.position.y, 2) +
Math.pow(hitPose.transform.position.z - camera.position.z, 2)
);
return distance < 2; // Only consider hits within 2 meters
});
if (filteredResults.length > 0) {
const hit = filteredResults[0];
const hitPose = hit.getPose(xrSession.referenceSpace);
if (hitPose) {
// Update object position based on the filtered result
virtualObject.position.set(hitPose.transform.position.x, hitPose.transform.position.y, hitPose.transform.position.z);
virtualObject.quaternion.set(hitPose.transform.orientation.x, hitPose.transform.orientation.y, hitPose.transform.orientation.z, hitPose.transform.orientation.w);
}
}
}
๋ค๋ฅธ ์ฐธ์กฐ ๊ณต๊ฐ ์ฌ์ฉ
์ฐธ์กฐ ๊ณต๊ฐ(viewerSpace, local, localFloor ๋๋ ๊ธฐํ ์ฌ์ฉ์ ์ง์ ๊ณต๊ฐ)์ ์ ํ์ ํํธ ํ
์คํธ ๊ฒฐ๊ณผ๊ฐ ํด์๋๋ ๋ฐฉ์์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ๋ค์ ์ฌํญ์ ๊ณ ๋ คํ์ญ์์ค.
- viewerSpace: ๋ทฐ์ด์ ์์น๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ์ฌ์ฉ์์ ์์ ์ ์ง์ ์ฐ๊ฒฐ๋ ์ํธ ์์ฉ์ ๋ง๋๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
- local: XR ์์ (XR ์ธ์ ์ ์์์ )์ ๊ธฐ์ค์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๊ฐ์ฒด๊ฐ ์ค์ ํ๊ฒฝ์ ๊ณ ์ ๋์ด ์๋ ๊ฒฝํ์ ๋ง๋๋ ๋ฐ ์ ํฉํฉ๋๋ค.
- localFloor:
local๊ณผ ์ ์ฌํ์ง๋ง Y์ถ์ด ๋ฐ๋ฅ๊ณผ ์ ๋ ฌ๋ฉ๋๋ค. ์ด๋ ๋ฐ๋ฅ์ ๊ฐ์ฒด๋ฅผ ๋ฐฐ์นํ๋ ํ๋ก์ธ์ค๋ฅผ ๋จ์ํํฉ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ ์ฌํญ์ ๊ฐ์ฅ ์ ๋ง๋ ์ฐธ์กฐ ๊ณต๊ฐ์ ์ ํํ์ญ์์ค. ๋ค์ํ ์ฐธ์กฐ ๊ณต๊ฐ์ ์คํํ์ฌ ๋์๊ณผ ์ ํ ์ฌํญ์ ์ดํดํ์ญ์์ค.
ํํธ ํ ์คํธ๋ฅผ ์ํ ์ต์ ํ ์ ๋ต
ํํธ ํ ์คํธ๋ ํนํ ๋ณต์กํ ์ฅ๋ฉด์์ ๊ณ์ฐ ์ง์ฝ์ ์ธ ํ๋ก์ธ์ค์ผ ์ ์์ต๋๋ค. ๊ณ ๋ คํด์ผ ํ ๋ช ๊ฐ์ง ์ต์ ํ ์ ๋ต์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํํธ ํ ์คํธ ๋น๋ ์ ํ: ํ๋ ์๋ง๋ค ์ํํ๋ ๋์ ํ์ํ ๋๋ง ํํธ ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ์ฅ๋ฉด์ ์ ๊ทน์ ์ผ๋ก ์ํธ ์์ฉํ ๋๋ง ํํธ ํ ์คํธ๋ฅผ ์ํํ ์ ์์ต๋๋ค.
- ๊ฒฝ๊ณ ๋ณผ๋ฅจ ๊ณ์ธต(BVH) ์ฌ์ฉ: ๋ง์ ์์ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ํํธ ํ ์คํธ๋ฅผ ์ํํ๋ ๊ฒฝ์ฐ BVH๋ฅผ ์ฌ์ฉํ์ฌ ๊ต์ฐจ์ ๊ณ์ฐ์ ๊ฐ์ํํ๋ ๊ฒ์ ๊ณ ๋ คํ์ญ์์ค. three.js ๋ฐ Babylon.js์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ด์ฅ BVH ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค.
- ๊ณต๊ฐ ๋ถํ : ์ฅ๋ฉด์ ๋ ์์ ์์ญ์ผ๋ก ๋๋๊ณ ๊ต์ฐจ์ ์ด ํฌํจ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ ์์ญ์ ๋ํด์๋ง ํํธ ํ ์คํธ๋ฅผ ์ํํฉ๋๋ค. ์ด๋ฅผ ํตํด ํ์ธํด์ผ ํ๋ ๊ฐ์ฒด์ ์๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ต๋๋ค.
- ๋ค๊ฐํ ์ ์ค์ด๊ธฐ: ํ ์คํธํด์ผ ํ๋ ๋ค๊ฐํ ์๋ฅผ ์ค์ด๊ธฐ ์ํด ๋ชจ๋ธ์ ๊ธฐํํ์ ๊ตฌ์กฐ๋ฅผ ๋จ์ํํฉ๋๋ค. ํนํ ๋ชจ๋ฐ์ผ ์ฅ์น์์ ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
- WebWorker: ๊ณ์ฐ์ ์น ์์ ์์๊ฒ ์คํ๋ก๋ํ์ฌ ํํธ ํ ์คํธ ํ๋ก์ธ์ค๊ฐ ์ฃผ ์ค๋ ๋๋ฅผ ์ ๊ทธ์ง ์๋๋ก ํฉ๋๋ค.
ํ๋ซํผ ๊ฐ ๊ณ ๋ ค ์ฌํญ
WebXR์ ํ๋ซํผ ๊ฐ ํธํ์ ๋ชฉํ๋ก ํ์ง๋ง ๋ค์ํ ์ฅ์น์ ๋ธ๋ผ์ฐ์ ์์ ๋์์ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ ์ ์์ต๋๋ค. ๋ค์ ์ฌํญ์ ๋ช ์ฌํ์ญ์์ค.
- ์ฅ์น ๊ธฐ๋ฅ: ๋ชจ๋ ์ฅ์น๊ฐ ๋ชจ๋ WebXR ๊ธฐ๋ฅ์ ์ง์ํ๋ ๊ฒ์ ์๋๋๋ค. ๊ธฐ๋ฅ ๊ฐ์ง๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ธฐ๋ฅ์ ํ์ ํ๊ณ ๊ทธ์ ๋ฐ๋ผ ์ ํ๋ฆฌ์ผ์ด์ ์ ์กฐ์ ํ์ญ์์ค.
- ์ ๋ ฅ ํ๋กํ: ๋ค๋ฅธ ์ฅ์น์์ ๋ค๋ฅธ ์ ๋ ฅ ํ๋กํ(์: ์ผ๋ฐ ํฐ์น ์คํฌ๋ฆฐ, ํธ๋ ์ถ์ , ๊ฒ์ํจ๋)์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฌ๋ฌ ์ ๋ ฅ ํ๋กํ์ ์ง์ํ๊ณ ์ ์ ํ ๋์ฒด ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํ๋์ง ํ์ธํ์ญ์์ค.
- ์ฑ๋ฅ: ์ฑ๋ฅ์ ์ฅ์น์ ๋ฐ๋ผ ํฌ๊ฒ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ์ง์ํ๋ ค๋ ๊ฐ์ฅ ๋ฎ์ ์์ค์ ์ฅ์น์ ๋ํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ต์ ํํ์ญ์์ค.
- ๋ธ๋ผ์ฐ์ ํธํ์ฑ: Chrome, Firefox, Edge์ ๊ฐ์ ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์ ์ฑ์ ํ ์คํธํ๊ณ ์๋ํ๋์ง ํ์ธํ์ญ์์ค.
ํํธ ํ ์คํธ๋ฅผ ์ฌ์ฉํ๋ WebXR ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธ๋ก๋ฒ ์
๋ค์์ ํํธ ํ ์คํธ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ์ฌ ๋งค๋ ฅ์ ์ด๊ณ ์ง๊ด์ ์ธ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ง๋๋ WebXR ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ช ๊ฐ์ง ์์ ๋๋ค.
- IKEA Place (์ค์จ๋ด): ์ฌ์ฉ์๊ฐ AR์ ์ฌ์ฉํ์ฌ ์ง์ IKEA ๊ฐ๊ตฌ๋ฅผ ๊ฐ์์ผ๋ก ๋ฐฐ์นํ ์ ์์ต๋๋ค. ํํธ ํ ์คํธ๋ ๊ฐ๊ตฌ๋ฅผ ๋ฐ๋ฅ ๋ฐ ๊ธฐํ ํ๋ฉด์ ์ ํํ๊ฒ ๋ฐฐ์นํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- Sketchfab AR (ํ๋์ค): ์ฌ์ฉ์๊ฐ Sketchfab์ 3D ๋ชจ๋ธ์ AR๋ก ๋ณผ ์ ์์ต๋๋ค. ํํธ ํ ์คํธ๋ ๋ชจ๋ธ์ ์ค์ ์ธ๊ณ์ ๋ฐฐ์นํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- ์ฆ๊ฐ ์ด๋ฏธ์ง(๋ค์): ๋ง์ AR ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ฏธ์ง ์ถ์ ๊ณผ ํํธ ํ ์คํธ๋ฅผ ๊ฒฐํฉํ์ฌ ๊ฐ์ ์ฝํ ์ธ ๋ฅผ ์ค์ ์ธ๊ณ์ ํน์ ์ด๋ฏธ์ง ๋๋ ๋ง์ปค์ ๊ณ ์ ํฉ๋๋ค.
- WebXR ๊ฒ์(๊ธ๋ก๋ฒ): ๋ง์ ๊ฒ์์ด WebXR์ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ๋๊ณ ์์ผ๋ฉฐ, ๊ทธ ์ค ๋ง์ ๊ฒ์์ด ๊ฐ์ฒด ๋ฐฐ์น, ์ํธ ์์ฉ ๋ฐ ํ์์ ์ํด ํํธ ํ ์คํธ์ ์์กดํฉ๋๋ค.
- ๊ฐ์ ํฌ์ด(๊ธ๋ก๋ฒ): ์์น, ๋ฐ๋ฌผ๊ด ๋๋ ๋ถ๋์ฐ์ ๋ชฐ์ ํ ํฌ์ด๋ ์ข ์ข ์ฌ์ฉ์๊ฐ ๊ฐ์ ํ๊ฒฝ ๋ด์์ ํ์ํ๊ณ ์ํธ ์์ฉํ๋ ์์๋ฅผ ์ํด ํํธ ํ ์คํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๊ฒฐ๋ก
WebXR ํํธ ํ ์คํธ ๊ฒฐ๊ณผ ๋ฐ ๋ ์ด ์บ์คํ ์ฒ๋ฆฌ๋ฅผ ์๋ฌํ๋ ๊ฒ์ ์น์์ ๋งค๋ ฅ์ ์ด๊ณ ์ง๊ด์ ์ธ AR ๋ฐ VR ๊ฒฝํ์ ๋ง๋๋ ๋ฐ ํ์์ ์ ๋๋ค. ์ด ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ์ค๋ช ๋ ๊ธฐ๋ณธ ๊ฐ๋ ์ ์ดํดํ๊ณ ๊ธฐ์ ์ ์ ์ฉํจ์ผ๋ก์จ ๊ฐ์ ์ธ๊ณ์ ์ค์ ์ธ๊ณ๋ฅผ ์ํํ๊ฒ ํผํฉํ๊ฑฐ๋ ์์ฐ์ค๋ฝ๊ณ ์ง๊ด์ ์ธ ์ฌ์ฉ์ ์ํธ ์์ฉ์ผ๋ก ๋งค๋ ฅ์ ์ธ ๊ฐ์ ํ๊ฒฝ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์ฑ๋ฅ์ ์ํด ํํธ ํ ์คํธ ๊ตฌํ์ ์ต์ ํํ๊ณ ๋ชจ๋ ์ฌ์ฉ์๋ฅผ ์ํด ์ํํ ๊ฒฝํ์ ๋ณด์ฅํ๊ธฐ ์ํด ํ๋ซํผ ๊ฐ ํธํ์ฑ์ ๊ณ ๋ คํ์ญ์์ค. WebXR ์ํ๊ณ๊ฐ ๊ณ์ ๋ฐ์ ํจ์ ๋ฐ๋ผ ํํธ ํ ์คํธ API์ ๋ํ ์ถ๊ฐ ๋ฐ์ ๊ณผ ๊ฐ์ ์ ์์ํ์ฌ ๋ชฐ์ ํ ์น ๊ฐ๋ฐ์ ์ํ ๋ ๋ง์ ์ฐฝ์์ ์ธ ๊ฐ๋ฅ์ฑ์ ์ด์ด๊ฐ ๊ฒ์ ๋๋ค. ํญ์ ์ต์ WebXR ์ฌ์ ๋ฐ ๋ธ๋ผ์ฐ์ ์ค๋ช ์๋ฅผ ์ฐธ์กฐํ์ฌ ์ต์ ์ ๋ณด๋ฅผ ํ์ธํ์ญ์์ค.